package com.coincalf.wallet.blockchain;

import com.coincalf.framework.enums.TxStateEnum;
import com.coincalf.wallet.blockchain.bean.TransInfo;
import com.coincalf.wallet.blockchain.client.Web3jSingleton;
import com.coincalf.wallet.blockchain.util.AESUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.web3j.abi.TypeReference;
import org.web3j.abi.datatypes.Address;
import org.web3j.abi.datatypes.Function;
import org.web3j.abi.datatypes.Type;
import org.web3j.abi.datatypes.generated.Uint256;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.ECKeyPair;
import org.web3j.crypto.RawTransaction;
import org.web3j.crypto.TransactionEncoder;
import org.web3j.protocol.admin.Admin;
import org.web3j.protocol.core.DefaultBlockParameter;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.methods.response.EthGetTransactionCount;
import org.web3j.protocol.core.methods.response.EthSendTransaction;
import org.web3j.protocol.core.methods.response.Transaction;
import org.web3j.protocol.core.methods.response.TransactionReceipt;
import org.web3j.tx.ChainId;
import org.web3j.utils.Convert;
import org.web3j.utils.Numeric;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.Collections;
import java.util.Optional;

import static java.math.BigDecimal.ROUND_HALF_UP;

@Slf4j
public class EthService {

    /**
     * 获取当前最新区块高度
     *
     * @return
     * @throws IOException
     */
    public static BigInteger getNewestBlockNumber() throws IOException {
        BigInteger blockNumber = Web3jSingleton.getAdmin().ethBlockNumber().send().getBlockNumber();
        log.info("当前ETH区块高度={}", blockNumber.intValue());
        return blockNumber;
    }

    /**
     * 合约方法
     *
     * @param to    收账地址
     * @param value 转账额
     * @return Function
     */
    public static Function tranFunction(String to, BigInteger value) {
        Function function = null;
        try {
            function = new Function(
                    "transfer",
                    Arrays.asList(new Address(to), new Uint256(value)),
                    Collections.singletonList(new TypeReference<Type>() {
                    }));
        } catch (Exception e) {
            log.error("获取合约方法error e={}", e);
        }
        return function;
    }

    /**
     * 获取已成交nonce值
     *
     * @param from
     * @return
     */
    public static BigInteger getNonceLatest(String from) {
        EthGetTransactionCount transactionCount;
        BigInteger nonce = null;
        try {
            transactionCount = Web3jSingleton.getWeb3j().ethGetTransactionCount(from, DefaultBlockParameterName.LATEST).send();
            nonce = transactionCount.getTransactionCount();
        } catch (IOException e) {
            log.error("获取nonce error e={}", e);
        }
        return nonce;
    }


    /**
     * 发送ETH交易
     *
     * @param signHexTx 签名的交易
     * @return
     */
    public static String sendTx(String signHexTx) {
        EthSendTransaction result;
        try {
            result = Web3jSingleton.getWeb3j().ethSendRawTransaction(signHexTx).send();
        } catch (Exception e) {
            throw new BlockException(e.getMessage());

        }
        if (result.getError() != null) {
            String message = result.getError().getMessage();
            if (message.contains("insufficient funds for gas * price + value")) {
                //也可能是私钥错误
                throw new BlockException("insufficient funds for gas or prk error");
            } else if (message.contains("nonce too low")) {
                throw new BlockException("nonce too low error");
            } else if (message.contains("exceeds block gas limit")) {
                throw new BlockException("exceeds block gas limit");
            } else {
                throw new BlockException(message);
            }
        }
        log.info("交易广播成功 txId={}", result.getTransactionHash());
        return result.getTransactionHash();
    }

    public static String toEth(String value) {
        BigDecimal bigDecimal = new BigDecimal(value);
        BigDecimal divide = bigDecimal.divide(BlockConstant.WEI, 8, RoundingMode.HALF_UP);
        return divide.toString();
    }

    /**
     * 说明：USDT-ERC20精度并非10的18次方
     *
     * @param value
     * @return
     */
    public static String usdtErcToEth(String value) {
        BigDecimal bigDecimal = new BigDecimal(value);
        BigDecimal divide = bigDecimal.divide(BlockConstant.USDT_ERC20_RATIO, 8, RoundingMode.HALF_UP);
        return divide.toString();
    }

    public static BigDecimal getEthBalance(String address) {
        log.info("查询eth余额 address={}", address);
        try {
            //区块同步完成
            BigInteger ethBalance;
            ethBalance = Web3jSingleton.getAdmin().ethGetBalance(address, DefaultBlockParameter.valueOf("latest")).send().getBalance();
            BigDecimal amount = new BigDecimal(ethBalance).divide(BlockConstant.WEI, 8, RoundingMode.DOWN);
            log.info("address={} ethBalance={}", address, amount);
            return amount;
        } catch (Exception e) {
            log.error("获取以太坊余额异常 address={} e={}", address, e);
            return null;
        }
    }

    /**
     * ETH转账交易
     *
     * @param bean
     * @return
     */
    public static TransInfo trans(TransInfo bean) {
        TransInfo transInfo = new TransInfo();
        log.info("ETH交易开始 from={}, to={}, amount={}, gasPrice={} GWEI", bean.getFromAddress(), bean.getToAddress(), bean.getAmount(), bean.getFee());
        try {
            String privateKey = bean.getPrivateKey();
            privateKey = AESUtil.decrypt(privateKey, BlockConstant.AES_KEY);
            //获取nonce
            BigInteger nonce = Web3jSingleton.getWeb3j().ethGetTransactionCount(bean.getFromAddress(), DefaultBlockParameterName.PENDING).send().getTransactionCount();
            String txId = transaction(bean.getFromAddress(), privateKey, nonce, bean.getToAddress(), bean.getAmount(), bean.getFee());
            log.info("txId={}", txId);
            if (StringUtils.isEmpty(txId)) {
                log.info("ETH交易失败 from={}, to={}, amount={}, gasPrice={} GWEI", bean.getFromAddress(), bean.getToAddress(), bean.getAmount(), bean.getFee());
                transInfo.setStatus(TxStateEnum.FAIL.getStatus());
            } else {
                transInfo.setStatus(TxStateEnum.TO_BE_CONFIRMED.getStatus());
                transInfo.setAmount(bean.getAmount());
                transInfo.setTxHash(txId);
                transInfo.setNonce(nonce.longValue());
            }
        } catch (Exception e) {
            log.info("ETH交易失败 from={}, to={}, amount={}, gasPrice={} GWEI  error={}",
                    bean.getFromAddress(), bean.getToAddress(), bean.getAmount(), bean.getFee(), e.getMessage());
            throw new BlockException(e.getMessage());
        }
        return transInfo;
    }

    public static TransInfo query(String txId) {
        TransInfo bean = new TransInfo();
        bean.setTxHash(txId);
        Transaction transaction;
        Admin admin = Web3jSingleton.getAdmin();
        try {
            transaction = admin.ethGetTransactionByHash(txId).send().getTransaction().orElse(null);
            if (transaction == null) {
                //没有查询到交易 不代表交易失败 可能是刚刚广播出去
                log.info("ETH交易未同步 txId={}", txId);
                bean.setStatus(TxStateEnum.TO_BE_CONFIRMED.getStatus());
                return bean;
            }

            Optional<TransactionReceipt> transactionReceipt = admin.ethGetTransactionReceipt(txId).send().getTransactionReceipt();
            Boolean success = transactionReceipt.map(TransactionReceipt::getStatus)
                    // 0x0失败 0x1成功 null失败或者未知
                    .map("0x1"::equals).get();

            BigInteger confirmations = EthService.getNewestBlockNumber().subtract(transaction.getBlockNumber()).add(BigInteger.valueOf(1));
            bean.setConfirmations(confirmations.longValue());
            BigDecimal gasPrice = new BigDecimal(String.valueOf(transaction.getGasPrice()));
            BigInteger gasUsed = transactionReceipt.map(TransactionReceipt::getGasUsed).get();
            BigDecimal fee = gasPrice.multiply(BigDecimal.valueOf(gasUsed.longValue())).divide(BlockConstant.WEI, 8, ROUND_HALF_UP);
            log.info("ETH交易矿工费={}", fee);
            bean.setFee(fee);
            bean.setBlockNumber(transaction.getBlockNumber().longValue());
            if (success) {
                bean.setStatus(TxStateEnum.SUCCESS.getStatus());
            } else {
                bean.setStatus(TxStateEnum.FAIL.getStatus());
            }
        } catch (Exception e) {
            //查询失败 不代表交易失败
            bean.setStatus(TxStateEnum.TO_BE_CONFIRMED.getStatus());
            log.error("ETH交易查询error txId={} error={}", txId, e);
        }
        return bean;
    }

    /**
     * 构建ETH离线签名交易
     *
     * @return 交易hash
     */
    private static String transaction(String fromAddress, String privateKey, BigInteger fromNonce, String toAddress, BigDecimal amount, BigDecimal fee) throws BlockException {
        //设置手续费 矿工可接受范围之内
        BigInteger gasPrice = Convert.toWei(fee, Convert.Unit.GWEI).toBigInteger();
        BigInteger gasLimit = BigInteger.valueOf(21000);
        toAddress = toAddress.toLowerCase();
        BigInteger value = Convert.toWei(amount, Convert.Unit.ETHER).toBigInteger();
        //data会被写到ETH链上，不要随便添加，否则会增加gas
        String data = "";
        //获取私钥进行交易签名
        String signedData = signTransaction(fromNonce, gasPrice, gasLimit, toAddress, value, data, privateKey);
        log.info("ETH交易签名成功(不等于广播交易成功) from={} to={} amount={} fee={}",
                fromAddress, toAddress, amount, fee);
        return EthService.sendTx(signedData);
    }

    /**
     * ETH交易签名
     *
     * @return 签名信息
     */
    private static String signTransaction(BigInteger nonce, BigInteger gasPrice, BigInteger gasLimit, String to,
                                          BigInteger value, String data, String privateKey) {
        byte[] signedMessage;
        RawTransaction rawTransaction = RawTransaction.createTransaction(nonce, gasPrice, gasLimit, to, value, data);
        if (privateKey.startsWith("0x")) {
            privateKey = privateKey.substring(2);
        }
        ECKeyPair ecKeyPair = ECKeyPair.create(new BigInteger(privateKey, 16));
        Credentials credentials = Credentials.create(ecKeyPair);
        //signedMessage = TransactionEncoder.signMessage(rawTransaction, ChainId.MAINNET, credentials);
        signedMessage = TransactionEncoder.signMessage(rawTransaction, ChainId.RINKEBY, credentials);
        String hexValue = Numeric.toHexString(signedMessage);
        log.info("ETH交易签名成功 hexStr={}", hexValue);
        return hexValue;
    }

}
